package airacrobats.gui.container;

import airacrobats.control.Controller;
import airacrobats.control.PlayerController;
import airacrobats.game.Camera;
import airacrobats.game.Stage;
import airacrobats.io.data.JWavefrontModel;
import airacrobats.game.object.GameObject;
import airacrobats.game.Smoker;
import airacrobats.sound.RadioSound;
import airacrobats.sound.Sound;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.RenderingHints;
import java.util.List;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLJPanel;
import javax.sound.sampled.LineUnavailableException;
import javax.vecmath.Point3f;

/**
 * This is the game container panel, where things are drawn.
 */
public class GameContainer extends GLJPanel implements GLEventListener
{
    /**
     * List of objects.
     */
    protected List<GameObject> objects;
    /**
     * Camera.
     */
    protected Camera camera;
    /**
     * The controllers.
     */
    protected List<Controller> controllers;
    /**
     * The player controller.
     */
    protected PlayerController controller;
    /**
     * Last time the screen was updated, in nanoseconds.
     */
    protected long lastTime;
    /**
     * The time elapsed so far, in milliseconds.
     */
    protected long time;
    /**
     * The stage running.
     */
    protected Stage stage;
    /**
     * Flag indicating if the game has started.
     */
    protected boolean started;
    /**
     * Radio sounds.
     */
    private List<RadioSound> radioSounds;
    /**
     * Current radio sound.
     */
    private RadioSound radioSound;
    /**
     * The smokers.
     */
    private List<Smoker> smokers;
    /**
     * Freeze (virtual pause) flag.
     */
    private boolean freezed;

    /**
     * @param capabilities Capabilities.
     * @param dimension Size of the panel.
     * @param objects Objects of the screen.
     * @param camera The camera.
     * @param controllers The controllers.
     * @param radioSounds The radio sounds.
     * @param controller The player controller.
     * @param stage The stage.
     * @param smokers The smokers.
     */
    public GameContainer(GLCapabilities capabilities, Dimension dimension, List<GameObject> objects,
            Camera camera, List<Controller> controllers, List<RadioSound> radioSounds,
            PlayerController controller, Stage stage, List<Smoker> smokers)
    {
        super(capabilities);
        
        this.objects = objects;
        this.camera = camera;
        this.controllers = controllers;
        this.radioSounds = radioSounds;
        this.controller = controller;
        this.stage = stage;
        this.smokers = smokers;
        
        setPreferredSize(dimension);
        setMinimumSize(dimension);
        setDoubleBuffered(true);
        addGLEventListener((GLEventListener) this);

        setLayout(new GridBagLayout());
    }

    /**
     * Sets the game to started, allowing the time to start running.
     * @throws LineUnavailableException Thrown if a sound to play fails.
     */
    public void setStarted() throws LineUnavailableException
    {
        started = true;
    }

    /**
     * @return The started flag.
     */
    public boolean isStarted()
    {
        return started;
    }

    /**
     */
    @Override
    protected void paintComponent(Graphics g)
    {
        g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        super.paintComponent(g);      
    }

    /**
     */
    public void init(GLAutoDrawable drawable)
    {
        GL2 gl = drawable.getGL().getGL2();

        gl.glEnable(GL2.GL_LIGHTING);
        gl.glEnable(GL2.GL_LIGHT0);
        gl.glEnable(GL2.GL_DEPTH_TEST);
        gl.glEnable(GL2.GL_RESCALE_NORMAL);
        gl.glShadeModel(GL2.GL_SMOOTH);
        gl.glEnable(gl.GL_BLEND);
        gl.glBlendFunc(GL2.GL_ONE_MINUS_SRC_ALPHA, GL2.GL_SRC_ALPHA);
        gl.glEnable(GL2.GL_POLYGON_SMOOTH);
        gl.glEnable(GL2.GL_POINT_SMOOTH);
        gl.glEnable(GL2.GL_LINE_SMOOTH);
        gl.glEnable(gl.GL_CULL_FACE);
        gl.glCullFace(gl.GL_CW);
        for (GameObject object : objects)
        {
            object.compileModels(drawable, JWavefrontModel.WF_MATERIAL | JWavefrontModel.WF_TEXTURE | JWavefrontModel.WF_SMOOTH);
            object.compileShadowModels(drawable, JWavefrontModel.WF_MATERIAL | JWavefrontModel.WF_SMOOTH);
        }
        for (Smoker smoker : smokers)
        {
            smoker.getModel().compile(drawable, JWavefrontModel.WF_MATERIAL | JWavefrontModel.WF_SMOOTH);
        }

        setLight(gl);
        
        gl.glFogi(GL2.GL_FOG_MODE, GL2.GL_LINEAR);
        gl.glFogfv(GL2.GL_FOG_COLOR, new float[] {.96f, 0.96f, 0.96f, 0.96f}, 0);
        gl.glFogf(GL2.GL_FOG_DENSITY, 0.3f);
        gl.glHint(GL2.GL_FOG_HINT, GL2.GL_DONT_CARE);
        gl.glFogf(GL2.GL_FOG_START, 100.0f);
        gl.glFogf(GL2.GL_FOG_END, 200.0f);
        gl.glEnable(GL2.GL_FOG);
    }

    /**
     * The display method is the main loop method of the game. Here, things about
     * the game are updated and drawn.
     * @param drawable The drawable context.
     */
    public void display(GLAutoDrawable drawable)
    {
        long deltaTime = updateTime();

        stage.setRemainingTime(stage.getRemainingTime() - deltaTime / 1000000);

        for (Controller stageController : controllers)
        {         
            int score = stageController.update(time, deltaTime);
            if (stageController == controller)
                stage.setScore(stage.getScore() + score);
        }
        
        GL2 gl = drawable.getGL().getGL2();

        gl.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
        gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);

        // The camera real position is the position of the camera plus its vision structure.
        Point3f cameraRealPosition = new Point3f(camera.getPosition());
        cameraRealPosition.x += camera.getVisionStructure().x;
        cameraRealPosition.y += camera.getVisionStructure().y;
        cameraRealPosition.z += camera.getVisionStructure().z;

        try
        {
            camera.getGameObject().update(deltaTime, time, cameraRealPosition);
            camera.update(deltaTime, time);
        }
        catch (Exception ex)
        {
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(null, ex);
        }

        if (radioSounds != null && !radioSounds.isEmpty())
        {
            if (time >= radioSounds.get(0).getTime())
            {
                radioSound = radioSounds.get(0);
                try
                {
                    radioSound.play();
                }
                catch (LineUnavailableException ex)
                {
                    Thread.getDefaultUncaughtExceptionHandler().uncaughtException(null, ex);
                }
                radioSounds.remove(0);
            }
        }

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();

        camera.transformProjection(drawable);

        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();

        camera.transformModelview(drawable);

        updateLight(gl);

        try
        {
            for (GameObject object : objects)
            {
                if (camera.getGameObject() != object)
                    object.update(deltaTime, time, cameraRealPosition);
                object.draw(drawable);
            }
        }
        catch (Exception ex)
        {
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(null, ex);
        }

        for (Smoker smoker : smokers)
            smoker.updateAndDraw(drawable, deltaTime);
    }

    /**
     */
    public void dispose(GLAutoDrawable drawable)
    {

    }

    /**
     */
    public void reshape(GLAutoDrawable glad, int i, int i1, int i2, int i3)
    {
        
    }

    /**
     * Stops the camera and all object sounds.
     */
    public void stopSounds()
    {
        if (camera.getSound() != null)
            camera.getSound().stop();

        if (radioSound != null)
            radioSound.stop();
        
        for (GameObject object : objects)
        {
            for (Sound sound : object.getSounds())
                sound.stop();
        }
    }

    /**
     * @param freezed The new freezed flag.
     */
    public void setFreezed(boolean freezed)
    {
        this.freezed = freezed;
    }

    /**
     * Updates the time variables.
     * @return The delta time.
     */
    protected long updateTime()
    {
        long deltaTime = 0;
        if (started && stage.getRemainingTime() > 0 && !freezed)
        {
            if (lastTime == 0)
                lastTime = System.nanoTime();
            deltaTime = System.nanoTime() - lastTime;
            lastTime += deltaTime;
        }

        time += deltaTime / 1000000.0f;

        return deltaTime;
    }

    /**
     * Updates the light direction on the current matrix.
     * @param gl The OpenGL context.
     */
    protected void updateLight(GL2 gl)
    {
        float[] position = new float[] { 0.0f, 1.0f, 0.0f, 0.0f };

        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, position, 0);
    }

    /**
     * Sets the light of the game.
     * @param gl The OpenGL context object.
     */
    private void setLight(GL2 gl)
    {
        float[] ambientLight = { 0.7f, 0.7f, 0.7f, 1.0f };
        float[] difuseLight = new float[] { 0.75f, 0.75f, 0.75f, 1.0f };
        float[] specularLight = new float[] { 0.7f, 0.7f, 0.7f, 1.0f };
        float[] position = new float[] { 0.0f, 1.0f, 0.0f, 0.0f };

        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambientLight, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, difuseLight, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, specularLight, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, position, 0);
    }
}
